import { Buffer } from "buffer";
import * as WebSocket from "ws";
import { StreamInfo,StreamData } from "./loadType";
import { EventEmitter } from "events";

import dotenv from 'dotenv';
dotenv.config();

//------------------------- config -------------------------
const HEARTBEAT_INTERVAL:number = 30000;// 30s
const HEARTBEAT_TIMEOUT:number = HEARTBEAT_INTERVAL + 10000;// +10s
const WS_SERVER_PORT:number = parseInt(process.env.WS_SERVER_PORT || '9090', 10);
const CREATE_KEY: string = "Warn: Don`t use the value. (Except for me, Who used who to fuck the dog.)";
//------------------------- config -------------------------
//------------------------- https + ws -------------------------
// import { createServer as createHttpsServer } from "https";
// import { readFileSync } from "fs";
// const server = createHttpsServer({
//     cert: readFileSync('/path/to/cert.pem'),
//     key: readFileSync('/path/to/key.pem')
// });
// const server = new WebSocketServer({ server });
//------------------------- https + ws -------------------------

//------------------------- http + ws -------------------------
// import { createServer as createHttpServer } from "http";
// const server = createHttpServer();
// const server = new WebSocketServer({ server });
//------------------------- http + ws -------------------------

export class WebSocketClient{
    private static staticSocketId:number = 1;
    public readonly socketId:string;
    private ip:any;
    private server:WebSocketServer;
    private socket:WebSocket;
    public isAlive = true;
    private isBroken = false;
    constructor(key:string,ip:any,server:WebSocketServer,socket: WebSocket) {
        if(key !== CREATE_KEY){
            throw new Error("Warn: Don`t constructor the class.");
        }
        let self = this;
        this.ip = ip;
        this.socketId = (Buffer.from("[" + (WebSocketClient.staticSocketId++).toString() + "][" +(Date.now()).toString() + "][" +this.ip.toString() + "]")).toString('base64');
        this.server = server;
        this.socket = socket;
        this.socket.on('close', function close(code: number, reason: string) {
            console.log(`!!![client.close]!!!${self.socketId}`);
            console.log(code);
            console.log(reason);
            this.close();
        });
        this.socket.on('error', function error(error: Error) {
            console.log(`!!![client.close]!!!${self.socketId}`);
            console.log(error);
            this.close();
        });
        this.socket.on('message', function message(message: string | Buffer | ArrayBuffer | Buffer[]) {
            // console.log(`!!![client.message]!!!${self.socketId}`);
            // console.log(message);
            let info: any ;
            try {
                info = StreamInfo.decode(Buffer.from(message as Buffer));
            } catch (error) {
                console.log(error);
                return;
            }
            console.log(info);
            // if (StreamInfo.verify(info)){
                // console.log(`decoded = ${JSON.stringify(info)}`);
                self.server.emit("StreamInfo", self, info);
            // }
        });

        this.socket.on('open', function open() {
            console.log(`!!![client.open]!!!${self.socketId}`);
        });

        this.socket.on('ping', function ping(data: Buffer) {
            console.log(`!!![client.ping]!!!${self.socketId}`);
            console.log(data);
        });

        this.socket.on('pong', function pong(data: Buffer) {
            // console.log(`!!![client.pong]!!!${self.socketId}`);
            // console.log(data);
            self.isAlive = true;
        });

        this.socket.on('unexpected-response', function unexpected_response(request: any, response: any) {
            console.log(`!!![client.unexpected-response]!!!${self.socketId}`);
            console.log(request);
            console.log(response);
        });

        this.socket.on('upgrade', function upgrade(response: any) {
            console.log(`!!![client.upgrade]!!!${self.socketId}`);
            console.log(response);
        });
    }

    public ping() {
        this.socket.ping();
    }

    public terminate() {
        this.socket.terminate();
    }
    public send(data:any){
        if(this.isBroken) return;
        this.socket.send(data);
    }
    private close(){
        this.isBroken = true;
        this.server.delClient(this.socketId);
    }
}

export class WebSocketServer extends EventEmitter{
    private address: any;
    private clients: Map<string,WebSocketClient> = new Map<string,WebSocketClient>();
    private server: WebSocket.Server;
    private interval: NodeJS.Timeout;
    private static instance:WebSocketServer|null = null;
    public static get Instance():WebSocketServer{
        if (WebSocketServer.instance === null) {
            WebSocketServer.instance = new WebSocketServer(CREATE_KEY);
        }
        return WebSocketServer.instance;
    }
    constructor(key:string){
        super();
        if (WebSocketServer.instance !== null) {
            throw new Error("Warn: Instance not is NULL");
        }
        if (key !== CREATE_KEY) {
            throw new Error("Warn: Don`t constructor the class.");
        }
        let self = this;
        this.server = new WebSocket.Server({ port: WS_SERVER_PORT });
        this.server.on('close', function close(): void {
            console.log('>>>[server.close]<<<');
        });

        this.server.on('connection', function connection(socket: any, request: any) {
            let ip:any = request.connection.remoteAddress;
            const xForwardedFor: any = request.headers['x-forwarded-for'];
            if (xForwardedFor && typeof (xForwardedFor) == "string" && xForwardedFor.length > 0) {
                ip = xForwardedFor.split(/\s*,\s*/)[0];
            }
            console.log(">>>[server.connection]<<<");
            console.log(`new websocket client[${ip}]`);
            // console.log(socket);
            // console.log(request);
            
            const client: WebSocketClient = new WebSocketClient(CREATE_KEY,ip, self, socket);
            const socketId: string = client.socketId;
            self.clients.set(socketId,client);
        });

        this.server.on('close', function close(): void {
            console.log('>>>[server.close]<<<');
        });

        this.server.on('headers', function headers(headers: any, request: any): void {
            // console.log('>>>[server.headers]<<<');
            // console.log(headers);
            // console.log(request);
        });

        this.server.on('listening', function listening(): void {
            console.log(`>>>[server.listening]<<<`);
            self.address = self.server.address();
            console.log(`start listen [${JSON.stringify(self.server.address())}]`);
        });

        this.interval = setInterval(function ping() {
            self.clients.forEach((client:WebSocketClient,value:string) => {
                if (client.isAlive === false) return client.terminate();
                client.isAlive = false;
                client.ping();
            }) ;
        }, HEARTBEAT_INTERVAL);
    };
    public close(){
        clearInterval(this.interval);
        //TODO
    };
    public delClient(socketId:string){
        this.clients.delete(socketId);
        this.emit("clientBroken", socketId);
    };
}

process.on('exit', (code: any) => {
    console.log(`About to exit with code: ${code}`);
    WebSocketServer.Instance.close();
});

//------------------------- http/s + ws -------------------------
// server.listen(wsPort, () => {
//     console.log("listen port: " + httpPort);
// });
//------------------------- http / s + ws-------------------------